<?php
/**
 * @link https://www.kancloud.cn/cleverstone/ymb2
 * @copyright Copyright (c) 2020 Yii Manager Software LLC
 */

namespace backend\controllers;

use common\helper\App;
use common\models\AdminService;
use Yii;
use yii\base\UserException;
use yii\web\Response;
use builder\helper\T;
use builder\helper\Url;
use builder\base\BaseController;
use common\models\Admin;
use common\models\TlogService;

/**
 * 站点
 * @author cleverstone
 * @since ym2.0
 */
class SiteController extends BaseController
{
    // 定义尝试登录次数缓存标识
    const ATTEMPT_CACHE_KEY = 'login_size';

    // 定义尝试登录次数缓存有效期
    const ATTEMPT_CACHE_DURATION = 24 * 3600;

    // 定义最大尝试登录次数
    const MAX_ATTEMPT_SIZE = 10;

    // 定义超出尝试次数后账号临时锁定时间
    const LOCK_DURATION = 5 * 60;

    // 定义未活动下登录保持会话有效期
    const REMEMBER_ME = 3 * 24 * 3600;

    public $actionVerbs = [
        'quit' => ['GET', 'POST'],
        'login' => ['GET', 'POST'],
    ];
    public $publicActions = ['login'];
    public $undetectedActions = ['quit'];

    /**
     * 后置操作
     * @param \yii\base\Action $action
     * @param Response $result
     * @return mixed
     */
    public function afterAction($action, $result)
    {
        $id = $action->id;
        if (!strcasecmp($id, 'login') && $this->isPost) {
            $data = $result->data;
            if (is_array($data)) {
                $message = !empty($data['msg']) ? $data['msg'] : '-';
            } else {
                $message = (string)$data;
            }

            $identify = $this->user->identity;
            $userId = $identify ? $identify->getId() : 0;
            // 记录操作日志
            TlogService::write($userId, 'site/login', '账号登录', $message);
        }

        return parent::afterAction($action, $result);
    }

    /**
     * 登录
     * @return string|\yii\web\Response
     * @throws \yii\base\Exception
     */
    public function actionLogin()
    {
        if ($this->isGet) {
            if (!$this->user->isGuest) {
                return $this->goHome();
            }

            $this->layout = 'blank';

            return $this->render('login');
        } else {
            $bodyParams = $this->filteredPost;

            $validateModel = new Admin();
            $validateModel->setScenario('SCE_Admin_Login');
            $validateModel->load($bodyParams);
            if (!$validateModel->validate()) {
                return $this->asFail($validateModel->error);
            }

            $user = Admin::findByUsername($validateModel->username);
            if (empty($user)) {
                return $this->asFail('账号【'.$validateModel->username.'】错误。');
            }

            if ($user->status == Admin::STATUS_INACTIVE) {
                if (empty($denyEndTime = $user->deny_end_time)) {
                    return $this->asFail('您的账号【'.$user->username.'】已被永久冻结。');
                }
                $denyEndTime = strtotime($denyEndTime);
                $nowTime = T::now(false);
                if ($denyEndTime > $nowTime) {
                    $duration = Yii::$app->formatter->asRelativeTime($denyEndTime);
                    return $this->asFail('您的账号【'.$user->username.'】已被锁定，请'.$duration.'重试。');
                } else {
                    // 过期后,自动解除锁定
                    $user->status = Admin::STATUS_ACTIVE;
                    $user->deny_end_time = null;
                    $user->save(false);
                }
            }

            if (!$user->validatePassword($validateModel->password_hash)) {
                $surplusAttemptSize = $this->addAttemptSize($validateModel->username, $user);
                if ($surplusAttemptSize) {
                    $errorMsg = '账号【'.$user->username.'】密码错误，您还可以继续尝试'.$surplusAttemptSize.'次。';
                } else {
                    $lockDuration = Yii::$app->formatter->asDuration(self::LOCK_DURATION);
                    $errorMsg = '账号【'.$user->username.'】密码错误次数过多，已被锁定' . $lockDuration . '。';
                }

                return $this->asFail($errorMsg);
            }

            if ($this->user->login($user, self::REMEMBER_ME)) {
                // 更新SSO随机串
                $user->sso_key = AdminService::generateSSOKEY();
                $user->save(false);
                // 会话存储sso_key
                $this->session->set(App::params('admin.SSO.key'), $user->sso_key);
                // 重置尝试次数
                $this->clearAttemptSize($validateModel->username);
                // 获取重定向URL
                $referrer = $this->request->getReferrer();
                $queryStr = parse_url($referrer ?? '', PHP_URL_QUERY);
                parse_str($queryStr ?: '', $queryParams);
                $redirectUri = $queryParams['redirect'] ?? Yii::$app->defaultRoute;

                return $this->asOk('账号【'.$user->username.'】登录成功。', Url::to($redirectUri, ''));
            }

            return $this->asFail('账号【'.$user->username.'】登录失败。');
        }
    }

    protected function addAttemptSize($key, $user)
    {
        $cacheData = $this->cache->get(self::ATTEMPT_CACHE_KEY);

        if (empty($cacheData) || empty($cacheData[$key])) {
            $cacheData[$key] = [
                'size' => 1,
                'expire' => time() + self::ATTEMPT_CACHE_DURATION,
            ];
        } else {
            $value = $cacheData[$key];
            if ($value['expire'] <= time()) {
                $cacheData[$key] = [
                    'size' => 1,
                    'expire' => time() + self::ATTEMPT_CACHE_DURATION,
                ];
            } else {
                $cacheData[$key]['size']++;
            }
        }

        $totalAttemptSize = $cacheData[$key]['size'];
        $surplusAttemptSize = self::MAX_ATTEMPT_SIZE - $totalAttemptSize;

        if ($surplusAttemptSize <= 0) {
            $lockDuration = self::LOCK_DURATION;
            /* @var Admin $user */
            $user->status = Admin::STATUS_INACTIVE;
            $user->deny_end_time = date('Y-m-d H:i:s', strtotime("+ $lockDuration seconds"));
            $user->save(false);

            unset($cacheData[$key]);
        }

        $this->cache->set(self::ATTEMPT_CACHE_KEY, $cacheData);

        return max($surplusAttemptSize, 0);
    }

    protected function clearAttemptSize($key)
    {
        $cacheData = $this->cache->get(self::ATTEMPT_CACHE_KEY);
        if (!empty($cacheData) && !empty($cacheData[$key])) {
            unset($cacheData[$key]);

            $this->cache->set(self::ATTEMPT_CACHE_KEY, $cacheData);
        }
    }

    /**
     * 退出登录
     * @return \yii\web\Response
     * @throws UserException
     */
    public function actionQuit()
    {
        /** @var Admin $identify */
        $identify = $this->user->identity;
        $isGuest = $this->user->logout();
        if ($isGuest) {
            // 记录操作日志
            TlogService::write($identify->id, 'site/quit', '账号退出', '管理员账号【'.$identify->username.'】退出成功。');
            $referrerUri = $this->request->getQueryParam('referrer');
            return $this->goLogin($referrerUri ? ['redirect' => $referrerUri] : []);
        }

        throw new UserException('Logout failure. ');
    }
}